/**
 * 
 */
package qy.jalgotrade.technical;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

import com.google.common.math.DoubleMath;

import org.apache.commons.lang3.tuple.Pair;

import qy.jalgotrade.dataseries.DataSeries;
import qy.jalgotrade.utils.CommonUtils;
import qy.jalgotrade.utils.PySpecs;

/**
 * <pre>
 * Note:
 * Up to version 0.12 CrossAbove and CrossBelow were DataSeries.
 * In version 0.13 SequenceDataSeries was refactored to support specifying a limit to the amount
 * of values to hold. This was introduced mainly to reduce memory footprint.
 * This change had a huge impact on the way DataSeries filters were implemented since they were
 * mosly views and didn't hold any actual values. For example, a SMA(200) didn't hold any values at all
 * but rather calculate those on demand by requesting 200 values from the DataSeries being wrapped.
 * Now that the DataSeries being wrapped may not hold so many values, DataSeries filters were refactored
 * to an event based model and they will calculate and hold resulting values as new values get added to
 * the underlying DataSeries (the one being wrapped).
 * Since it was too complicated to make CrossAbove and CrossBelow filters work with this new model (
 * mainly because the underlying DataSeries may not get new values added at the same time, or one after
 * another) I decided to turn those into functions, cross_above and cross_below.
 * </pre>
 * 
 * TODO: to optimize
 * 
 * @author c-geo
 *
 */
public final class CrossUils {

	/**
	 * <pre>
	 * Checks for a cross above conditions over the specified period between two
	 * DataSeries objects.
	 * 
	 * It returns the number of times values1 crossed above values2 during the given
	 * period.
	 * cross_above: 返回指定范围内 values1 序列 丄穿 values2 序列的次数.
	 * </pre>
	 * 
	 * @param values1 The DataSeries that crosses.
	 * @param values2 The DataSeries being crossed.
	 * @return
	 */
	public static int crossAbove(DataSeries<Double> values1, DataSeries<Double> values2) {

		return crossAbove(values1, values2, -2, -1);
	}

	/**
	 * <pre>
	 * Checks for a cross above conditions over the specified period between two
	 * DataSeries objects.
	 * 
	 * It returns the number of times values1 crossed above values2 during the given
	 * period.
	 * cross_above: 返回指定范围内 values1 序列 丄穿 values2 序列的次数.
	 * </pre>
	 * 
	 * @param values1 The DataSeries that crosses.
	 * @param values2 The DataSeries being crossed.
	 * @param start   The start of the range.
	 * @return
	 */
	public static int crossAbove(DataSeries<Double> values1, DataSeries<Double> values2, int start) {

		return crossAbove(values1, values2, start, -1);
	}

	/**
	 * <pre>
	 * Checks for a cross above conditions over the specified period between two
	 * DataSeries objects.
	 * 
	 * It returns the number of times values1 crossed above values2 during the given
	 * period.
	 * cross_above: 返回指定范围内 values1 序列 丄穿 values2 序列的次数.
	 * </pre>
	 * 
	 * @param values1 The DataSeries that crosses.
	 * @param values2 The DataSeries being crossed.
	 * @param start   The start of the range.
	 * @param end     The end of the range.
	 * @return
	 */
	public static int crossAbove(DataSeries<Double> values1, DataSeries<Double> values2, int start, int end) {

		return _cross_impl(values1, values2, start, end,
		        (x) -> DoubleMath.fuzzyCompare(x, 0.0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH) > 0);
	}

	/**
	 * <pre>
	 * Checks for a cross below conditions over the specified period between two
	 * DataSeries objects.
	 * 
	 * It returns the number of times values1 crossed below values2 during the given
	 * period.
	 * cross_below: 返回指定范围内 values1 序列 下穿 values2 序列的次数.
	 * </pre>
	 * 
	 * @param values1 The DataSeries that crosses.
	 * @param values2 The DataSeries being crossed.
	 * @return
	 */
	public static int crossBelow(DataSeries<Double> values1, DataSeries<Double> values2) {

		return crossBelow(values1, values2, -2, -1);
	}

	/**
	 * <pre>
	 * Checks for a cross below conditions over the specified period between two
	 * DataSeries objects.
	 * 
	 * It returns the number of times values1 crossed below values2 during the given
	 * period.
	 * cross_below: 返回指定范围内 values1 序列 下穿 values2 序列的次数.
	 * </pre>
	 * 
	 * @param values1 The DataSeries that crosses.
	 * @param values2 The DataSeries being crossed.
	 * @param start   The start of the range.
	 * @return
	 */
	public static int crossBelow(DataSeries<Double> values1, DataSeries<Double> values2, int start) {

		return crossBelow(values1, values2, -2, -1);
	}

	/**
	 * <pre>
	 * Checks for a cross below conditions over the specified period between two
	 * DataSeries objects.
	 * 
	 * It returns the number of times values1 crossed below values2 during the given
	 * period.
	 * cross_below: 返回指定范围内 values1 序列 下穿 values2 序列的次数.
	 * </pre>
	 * 
	 * @param values1 The DataSeries that crosses.
	 * @param values2 The DataSeries being crossed.
	 * @param start   The start of the range.
	 * @param end     The end of the range.
	 * @return
	 */
	public static int crossBelow(DataSeries<Double> values1, DataSeries<Double> values2, int start, int end) {

		return _cross_impl(values1, values2, start, end,
		        (x) -> DoubleMath.fuzzyCompare(x, 0.0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH) < 0);
	}

	public static List<Double> computeDiff(List<Double> values1, List<Double> values2) {

		assert values1.size() == values2.size();

		// TODO: to optimize:
		List<Double> ret = new ArrayList<>();
		for (int i = 0; i < values1.size(); i++) {
			double v1 = values1.get(i);
			double v2 = values2.get(i);
			double diff;
			if (!Double.isNaN(v1) & !Double.isNaN(v2)) {
				diff = v1 - v2;
			} else {
				diff = Double.NaN;
			}
			ret.add(diff);
		}
		return ret;
	}

	private static Pair<List<Double>, List<Double>> _get_stripped(List<Double> values1, List<Double> values2,
	        boolean alignLeft) {

		List<Double> retValues1 = values1;
		List<Double> retValues2 = values2;
		if (values1.size() > values2.size()) {
			if (alignLeft) {
				retValues1 = values1.subList(0, values2.size());
			} else {
				retValues1 = values1.subList(values1.size() - values2.size(), values1.size());
			}
		} else if (values2.size() > values1.size()) {
			if (alignLeft) {
				retValues2 = values2.subList(0, values1.size());
			} else {
				retValues2 = values2.subList(values2.size() - values1.size(), values2.size());
			}
		}
		return Pair.of(retValues1, retValues2);
	}

	private static int _cross_impl(DataSeries<Double> values1, DataSeries<Double> values2, int start, int end,
	        Predicate<Double> signCheck) {

		// Get both set of values.
		Pair<List<Double>, List<Double>> tup = _get_stripped(PySpecs.slice(values1, start, end),
		        PySpecs.slice(values2, start, end), start > 0);

		// Compute differences and check sign changes.
		int ret = 0;
		List<Double> diffs = computeDiff(tup.getLeft(), tup.getRight());
		// XXX: qy-pyalgotrade:
//		diffs.removeIf((x) -> DoubleMath.fuzzyEquals(x, 0.0, CommonUtils.FUZZY_EQUAL_TOLERANCE_MATH));

		double prevDiff = Double.NaN;
		for (Double diff : diffs) {
			if (!Double.isNaN(prevDiff) && !signCheck.test(prevDiff) && signCheck.test(diff)) {
				ret++;
			}
			prevDiff = diff;
		}
		return ret;
	}

	/**
	 * 
	 */
	private CrossUils() {

	}
}
